#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>  // for socket(), waitpid()
#include <sys/socket.h>  // for socket()
#include <netinet/in.h>  // for sockaddr_in
#include <arpa/inet.h>  // for inet_addr()
#include <wait.h>  // for waitpid()
#include <signal.h>  // for signal()
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "Deamon.hpp"

#include "log.hpp"

#define CONVERT_ADDR(addr_ptr) ((struct sockaddr *)addr_ptr)

const static int default_backlog = 5;

class TcpServer
{
private:
    void _init()
    {
        // 1. create socket and file fd, which is essentially a file
        _listen_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_fd < 0)
        {
            ELOG("Failed to create socket");
            exit(1);
        }
        ILOG("Socket created successfully");
        // 2.1. fill the server address struct
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        // resolve binding failures due to server restart
        int optval = 1;
        setsockopt(_listen_fd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &optval, sizeof(optval));

        // 2.2. bind the socket to the local address
        if (bind(_listen_fd, CONVERT_ADDR(&local), sizeof(local)) < 0)
        {
            ELOG("Failed to bind socket to local address");
            exit(1);
        }
        ILOG("Socket bound to local address successfully");
        // 3. listen for incoming connections
        if (listen(_listen_fd, default_backlog) < 0)
        {
            ELOG("Failed to listen for incoming connections");
            exit(1);
        }
        ILOG("Socket is now listening for incoming connections");
    }

public:
    TcpServer(int port): _port(port), _is_running(false)
    {
        _init();
    }

    void start()
    {
        _is_running = true;
        // for v3, parent process don't need to wait and no Zombie process
        signal(SIGCHLD, SIG_IGN);
        // for v5, create thread pool
        using task_t = std::function<void()>;
        auto *pool = ThreadPool<task_t>::CreateThreadPool();
        Thread thread("pool_start", [&pool](){ pool->start(); });

        while (_is_running)
        {
            // 4. accept connection
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);
            DLOG("will accept incoming connection");
            int client_fd = accept(_listen_fd, CONVERT_ADDR(&client_addr), &client_addr_len);
            if(client_fd < 0)
            {
                WLOG("Failed to accept incoming connection");
                continue;
            }
            ILOG("Accepted incoming connection from {%s}", inet_ntoa(client_addr.sin_addr));

            // 5. provide service to client
            // 1) v1: for single-thread request-response
            // _service_v1(client_fd);
            // close(client_fd);

            // 2) v2: for multi-process request-response
            // pid_t id = fork();
            // if(id < 0)
            // {
            //     ELOG("Failed to fork child process");
            //     close(client_fd);
            //     continue;
            // }
            // else if(id == 0)
            // {
            //     // child process
            //     close(_listen_fd);

            //     if(fork() > 0)
            //     {
            //         // child process
            //         exit(EXIT_SUCCESS);
            //     }
            //     // grandchild process (fork() == 0) -- Orphan process will be recycled by OS
            //     _service_v1(client_fd);
            //     close(client_fd);
            // }
            // else
            // {
            //     // parent process
            //     close(client_fd);  // avoid file descriptor overflow

            //     ssize_t rid = waitpid(id, nullptr, 0);
            //     if(rid == id)
            //     {
            //         // do nothing
            //     }
            // }

            // 3) v3: for multi-process request-response (parent process don't need to wait and no Zombie process)
            // pid_t id = fork();
            // if(id < 0)
            // {
            //     ELOG("Failed to fork child process");
            //     close(client_fd);
            //     continue;
            // }
            // else if(id == 0)
            // {
            //     // child process
            //     close(_listen_fd);
            //     _service_v1(client_fd);
            //     close(client_fd);
            //     exit(EXIT_SUCCESS);
            // }
            // else
            // {
            //     // parent process
            //     close(client_fd);  // avoid file descriptor overflow
            //     // not need to wait, have singal handler to recycle orphan process
            // }

            // 4) v4: for multi-thread request-response
            // pthread_t tid;
            // ThreadData* data = new ThreadData(client_fd, &client_addr, this);
            // pthread_create(&tid, nullptr, _handlerRequest, static_cast<void*>(data));

            // 5) v5: for multi-thread using thread pool
            pool->enQueue(std::bind(&TcpServer::_service_v1, this, client_fd, client_addr));
        }
        thread.join();  // fatal attention: jion() must be called out of the loop, otherwise it will cause second client blocked
    }
private:
    // (begin)*********** for v4 *************
    class ThreadData
    {
    public:
        ThreadData(int fd, struct sockaddr_in* addr, TcpServer* server): _fd(fd), _addr(addr), _server(server)
        {
            // do nothing
        }

        int getFd() const
        {
            return _fd;
        }

        struct sockaddr_in* getAddr() const
        {
            return _addr;
        }

        TcpServer* getServer() const
        {
            return _server;
        }
    private:
        int _fd;
        struct sockaddr_in* _addr;
        TcpServer* _server;
    };

    static void* _handlerRequest(void* arg)
    {
        pthread_detach(pthread_self());
        ThreadData* data = static_cast<ThreadData*>(arg);
        data->getServer()->_service_v1(data->getFd(), *(data->getAddr()));
        delete data;
        return nullptr;
    }
    // (end)*********** for v4 *************

    void _service_v1(int client_fd, const InetAddr& addr)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));

        while(true)
        {
            ssize_t n = read(client_fd, buffer, sizeof(buffer));
            if(n > 0)
            {
                ILOG("Received message from [%s:%d]: %s", addr.IP().c_str(), addr.port(), buffer);
                std::string response = "[OK]{" + std::string(buffer) + "}";
                write(client_fd, response.c_str(), response.size());
            }
            else if(n == 0)
            {
                WLOG("Client closed connection, client fd: %d", client_fd);
                break;
            }
            else
            {
                ELOG("Failed to read from client, client fd: %d", client_fd);
                break;
            }
            bzero(buffer, sizeof(buffer));
        }
    }


private:
    int _listen_fd;
    int _port;
    bool _is_running;
};
